#ifndef _FASTApi_h_
#define _FASTApi_h_ 1

#include "types.h"
#include "common.h"
#include <string>
#include <sstream>

typedef enum eFAST_SIGN_MASK {
	SIGN_MASK_I32 = 0x40, SIGN_MASK_U32 = 0x00, SIGN_MASK_I64 = 0x40, SIGN_MASK_U64 = 0x00,
} eFAST_SIGN_MASK;

//#define SIGN_MASK_I32 0x40
//#define SIGN_MASK_U32 0x00
//#define SIGN_MASK_I64 0x40
//#define SIGN_MASK_U64 0x00

#define STR_ARGS(_x) _x, sizeof (_x)
#define STR_ARGS_LEN(_x) _x, sizeof (_x)

//////////////////////////////////////////////////////////////////////
#define STOP_BIT  0x80
#define VALUE_BIT  0x7F

typedef enum eFAST_TEMPLATE {
	eFAST_TID_TEMPLATE = 97, eFAST_HDR_TEMPLATE = 98, eFAST_TRL_TEMPLATE = 99,
} eFAST_TEMPLATE;

typedef enum eFAST_PMAP_BIT {
	ePMAP_BIT_OFF = 0x00, ePMAP_BIT_ON = 0x01,
} eFAST_PMAP_BIT;

typedef enum eFAST_OP {
	eFAST_OP_NONE = 0,
	eFAST_OP_DEFAULT,
	eFAST_OP_CONST,
	eFAST_OP_COPY,
	eFAST_OP_INCR,
	eFAST_OP_DELTA,
	eFAST_OP_TAIL,
} eFAST_OP;

typedef enum eFAST_TYPE {
	eFAST_TYPE_I32 = 0, /*Integer 32bit signed and unsigned*/
	eFAST_TYPE_U32, /*Integer 32bit unsigned*/
	eFAST_TYPE_I64, /*Integer 64bit signed and unsigned*/
	eFAST_TYPE_U64, /*Integer 64bit signed and unsigned*/
	eFAST_TYPE_ASCII, /*Ascii string*/
	eFAST_TYPE_DECMIAL, /*Decimal*/
	eFAST_TYPE_UNICODE, /*UNICODE string*/
	eFAST_TYPE_BYTEVECTOR, /*BYTE VECTOR*/
	eFAST_TYPE_GROUP, /*Group*/
	eFAST_TYPE_COMPONENT, /*Component Length + Repeating Group*, this is called sequence also*/
	eFAST_TYPE_NULL = 0,
} eFAST_TYPE;

typedef enum eFAST_ERROR {
	eFAST_ERR_NONE = 0,
	eFAST_ERR_CODEC = -1,
	eFAST_ERR_SIZE = -2,
	eFAST_ERR_VALUE = -3,
	eFAST_ERR_TAG_OP = -4,
	eFAST_ERR_TAG_TYPE = -5,
	eFAST_ERR_CALL_SEQ = -6,
	eFAST_ERR_IO = -7,
} eFAST_ERROR;

//////////////////////////////////////////////////////////////////////


#define TAG_MAX_SLOT     0x3ff // extendable to 0xfff
#define TAG_MAX_TID      0x3fff  // extendable to 0xfff
#define TAG_MAX_OP       0x7
#define TAG_MAX_REQ      0x1
#define TAG_MAX_TYPE     0xf

#define TAG_SHIFT_SLOT         0
#define TAG_SHIFT_TID          10
#define TAG_SHIFT_OP           24
#define TAG_SHIFT_REQ          27
#define TAG_SHIFT_TYPE         28

#define MAKE_TAG(type, req, op, tid, slot) \
   (u32 ((type) << TAG_SHIFT_TYPE) | u32 ((req) << TAG_SHIFT_REQ) | \
    u32((op)   << TAG_SHIFT_OP) | u32((tid)  << TAG_SHIFT_TID)  | \
	u32((slot) << TAG_SHIFT_SLOT))
//////////////////////////////////////////////////////////////////////

#define MAX_TAG 64
#define MAX_TID  4

#define MAX_PMAP_BYTES 16
#define MAX_PMAP_BITS  (7 * MAX_PMAP_BYTES)
#define MAX_MSG_SIZE  2048

typedef unsigned int fast_tag_t;

//////////////////////////////////////////////////////////////////////

enum eFAST_FIELDS {
	// 0-4
	eFAST_BASE_TID = 0,
	eFAST_MSG_TID = MAKE_TAG (eFAST_TYPE_U32, true, eFAST_OP_NONE, eFAST_BASE_TID, 0),
	eFAST_MSG_LEN = MAKE_TAG (eFAST_TYPE_U32, true, eFAST_OP_NONE, eFAST_BASE_TID, 0),
	eFAST_MSG_TYPE = MAKE_TAG (eFAST_TYPE_U32, true, eFAST_OP_COPY, eFAST_BASE_TID, 1),
};

//////////////////////////////////////////////////////////////////////

typedef struct FASTCV {
	FASTCV() {
		values[eFAST_TYPE_I32] = i32_values;
		values[eFAST_TYPE_U32] = u32_values;
		values[eFAST_TYPE_I64] = i64_values;
		values[eFAST_TYPE_U64] = u64_values;
		values[eFAST_TYPE_ASCII] = str_values;
		values[eFAST_TYPE_DECMIAL] = d_values;
	}
	i32 i32_values[MAX_TAG];
	u32 u32_values[MAX_TAG];
	u8* str_values[MAX_TAG];
	i64 i64_values[MAX_TAG];
	u64 u64_values[MAX_TAG];
	double d_values[MAX_TAG];
	u32 valid[MAX_TAG];
	void *values[5];
} FASTCV;

//////////////////////////////////////////////////////////////////////

typedef struct FASTPMap {
	u8 bits[MAX_PMAP_BITS];
	u32 size;
	u32 max_pos;
} FASTPMap;

typedef struct FASTBuffer {
	int fd;

	u8* head;
	u8* tail;
	u8* end;

	u8 data[MAX_MSG_SIZE];
} FASTBuffer;

typedef struct FASTGroup {
	FASTPMap pmap;
	u8 data[MAX_MSG_SIZE];
	u32 iSize;
} FASTGroup;

typedef struct FASTCodecError {
	const char* fn;
	char* text;

	fast_tag_t tag;
	eFAST_ERROR code;
} FASTCodecError;

//////////////////////////////////////////////////////////////////////

#define FAST_CODEC_MAGIC 0xC0DEC

typedef struct FASTCodec {
	u32 magic;

	const char* name;

	FASTPMap pmap;

	FASTBuffer msg;
	FASTBuffer input;
	FASTBuffer output;

	FASTCV cv[TAG_MAX_TID];

	// Config variables
	int count;
	//int skip_io;
	int verbose;

	// State variables
	int curr_tag;
	int in_message;

	FASTCodecError error[1];
} FASTCodec;


class FASTApi;

template<typename T>
int emitT(FASTApi *rthis, fast_tag_t tag, T data);

template<typename T>
int parseT(FASTApi *rthis, fast_tag_t tag, T *data, int sign_mask);

template<typename T, typename Q>
int encodeT(FASTApi *rthis, fast_tag_t tag, T value);

template<typename T, typename Q, typename S>
int decodeT(FASTApi *rthis, fast_tag_t tag, T* value, eFAST_SIGN_MASK mask_type, eFAST_SIGN_MASK mask_type_second, eFAST_TYPE fast_type, eFAST_TYPE fast_type_second);


//////////////////////////////////////////////////////////////////////

#ifdef __cplusplus
extern "C" {
#endif


class FASTApi {
public:

	FASTApi() {
		p_codec = NULL;
		cl_count = 0;
		cl_decode = 0;

		init_platform_io();
		p_codec = create_codec();
		assert(p_codec != NULL);
	}
	~FASTApi() {
		destroy_codec();
	}

	/**
	 * @defgroup fastapi fastapi functions
	 * @{
	 */

	/**
	 *	@brief Create and initialize the codec
	 *
	 *	@return	A pointer to opaque type FASTCodec
	 *   @remark This function will allocate a segment of memory on your behalf to manage the
	 *          codec state information.  The pointer returned should be treated as an opaque
	 *          handle, it is a required parameter to most other fastapi functions.
	 */
	FASTCodec* create_codec(void);

	/**
	 *	@brief Destroy the codec and release associated memory
	 *
	 *  @param	codec Pointer to a FASTCodec
	 *  @remark  This function will free the segment of memory on your behalf originally allocated by the
	 *          create_codec() function. The codec is invalid upon return.
	 */
	int destroy_codec();

	/**
	 *	@brief Reset the codec state for a specific tag
	 *
	 *	@param	codec Pointer to a FASTCodec
	 *	@param	tag The FASTCodecag we are reseting
	 *   @remark This function will reset the state of the codec for the specific tag.
	 */
	void fast_reset_state(fast_tag_t tag);

	/**
	 *	@brief Set the input FILE stream for a codec
	 *
	 *	@param	codec Pointer to a FASTCodec
	 *	@param	fptr FILE Pointer to a FILE stream
	 *   @remark The default input stream in stdin
	 */
	int set_codec_input(FILE* fptr);
	int set_codec_input(u8* pbuffer, unsigned int size);
	/**
	 *	@brief Set the output FILE stream for a codec
	 *
	 *	@param	codec Pointer to a FASTCodec
	 *	@param	fptr FILE Pointer to a FILE stream
	 *   @remark The default output stream in stdout
	 */
	int fast_set_codec_output(FILE* fptr);
	int get_codec_output(u8** pbuffer, unsigned int* psize);

	/**
	 *	@brief Make a FAST tag
	 */
	fast_tag_t fast_make_tag(eFAST_OP, eFAST_TYPE, u32 tid, u32 slot);

	/**
	 *	@brief Decode the first tag of a new message
	 *
	 *	@param	codec Pointer to a FASTCodec
	 *	@param	tag The FASTCodecag we are decoding
	 *  @remark This function must be called to decode the first tag of a new message.
	 *          The FASTCodecag should be the template ID
	 */
	int decode_message(fast_tag_t tag);

	/**
	 *	@brief Complete the decoding of a message
	 *
	 *	@param	codec Pointer to a FASTCodec
	 *	@param	tag The FASTCodecag we are decoding
	 *  @remark This function must be called after the last tag of a message has been decoded
	 */
	int decode_message_end(fast_tag_t tag);

	/**
	 *	@brief Decode a 32-bit signed integer
	 *
	 *	@param	codec Pointer to a FASTCodec
	 *	@param	tag The FASTCodecag we are decoding
	 *	@param	data Pointer to a i32 that will contain the result
	 */
	int decode_i32(fast_tag_t tag, i32* data);

	/**
	 *	@brief Decode a 32-bit unsigned integer
	 *
	 *	@param	codec Pointer to a FASTCodec
	 *	@param	tag The FASTCodecag we are decoding
	 *	@param	data Pointer to a u32 that will contain the result
	 */
	int decode_u32(fast_tag_t tag, u32* data);
	i32 decode_message_length(fast_tag_t tag, u32* data) {
		return parseT(this, tag, data, SIGN_MASK_U32);
	}
	int decode_group_count(fast_tag_t tag, u32* value);

	int decode_double(fast_tag_t tag, double* value);

	/**
	 *	@brief Decode an ASCII String
	 *
	 *	@param	codec Pointer to a FASTCodec
	 *	@param	tag The FASTCodecag we are decoding
	 *	@param	data Pointer to a char * that will contain the result
	 *	@param	size The maximum number of chars data can hold
	 *  @remark The resulting sting will NOT be NULL terminated, this function will only
	 *          fill the string with char's decoded from the input.
	 */
	int decode_string(fast_tag_t tag, u8* data, int size);

	/**
	 *	@brief Encode the first tag of a new message
	 *
	 *	@param	codec Pointer to a FASTCodec
	 *	@param	tag The FASTCodecag we are decoding
	 *  @remark This function must be called to encode the first tag of a new message.
	 *          The FASTCodecag should be the template ID
	 */
	int encode_message(fast_tag_t tag);

	/**
	 *	@brief Complete the encoding of a message
	 *
	 *	@param	codec Pointer to a FASTCodec
	 *	@param	tag The FASTCodecag we are encoding
	 *  @remark This function must be called after the last tag of a message has been encoded
	 */
	int encode_message_end(fast_tag_t tag);

	/**
	 *	@brief Encode a 32-bit signed integer
	 *
	 *	@param	codec Pointer to a FASTCodec
	 *	@param	tag The FASTCodecag we are decoding
	 *	@param	data i32 integer containing the value to encode
	 */
	int encode_i32(fast_tag_t tag, i32 data);
	int encode_i64(fast_tag_t tag, i64 data);

	/**
	 *	@brief Encode a 32-bit unsigned integer
	 *
	 *	@param	codec Pointer to a FASTCodec
	 *	@param	tag The FASTCodecag we are decoding
	 *	@param	data u32 integer containing the value to encode
	 */
	int encode_u32(fast_tag_t tag, u32 data);
	int encode_u64(fast_tag_t tag, u64 data);
	i32 decode_message_length(fast_tag_t tag, u32 value, u8* pbuffer);

	int encode_group_count(fast_tag_t tag, u32 data);

	int encode_double(fast_tag_t tag, double value);

	/**
	 *	@brief Encode an ASCII String
	 *
	 *	@param	codec Pointer to a FASTCodec
	 *	@param	tag The FASTCodecag we are decoding
	 *	@param	data char * containing the value to encode
	 *	@param	size The number of characters to encode
	 *  @remark This function will encode exactly size number of char's, it will not
	 *          not assume a NULL terminated string.
	 */
	int encode_string(fast_tag_t tag, u8* data, int size);

	/**
	 *	@brief Encode a 64-bit signed integer
	 *
	 *	@param	codec Pointer to a FASTCodec
	 *	@param	tag The FASTCodecag we are decoding
	 *	@param	data i64 integer containing the value to encode
	 */
	int decode_i64(fast_tag_t tag, i64* value);

	/**
	 *	@brief Encode a 64-bit unsigned integer
	 *
	 *	@param	codec Pointer to a FASTCodec
	 *	@param	tag The FASTCodecag we are decoding
	 *	@param	data i64 integer containing the value to encode
	 */
	int decode_u64(fast_tag_t tag, u64* value);
	/**
	 *	@brief Format and print the last reported codec error to a human readable string to a FILE stream
	 *
	 *   @param	codec Pointer to a FASTCodec
	 *   @param	fptr  FILE stream
	 *   @returns The error string
	 */

	int print_error(FILE* fptr);

	/**
	 *	@brief Convert the last reported codec error to a human readable string
	 *
	 *   @param	codec Pointer to a FASTCodec
	 *   @returns The error string
	 */
	const char* error_string();

	/**
	 *	@brief Convert a u8 byte arrray of characters to a u32
	 *
	 *	@param	 data Pointer to a u8 containing ASCII number
	 *	@param	 size Lenght of data
	 *  @returns The converted u32 number
	 *  @remark This function will convert an ASCII string of numbers to a binary value
	 *          All characters must be in range of '0' - '9'
	 */
	u32 ascii_to_u32(u8* data, int size);

	void reset_buffer(FASTBuffer* buffer);

	void init_buffer(FASTBuffer* buffer, int fd);

	int empty_buffer_pointer(FASTBuffer* buffer);

	int get_buffer_left(FASTBuffer* buffer);

	int get_buffer_size(FASTBuffer* buffer);

	int get_buffer_used(FASTBuffer* buffer);

	u32 get_tag_operator(u32 tag);

	u32 get_tag_required(u32 tag);

	u32 get_tag_slot(u32 tag);

	u32 get_tag_tid(u32 tag);

	u32 get_tag_type(u32 tag);

	FASTCV* get_tag_state(u32 tag);

	i32* get_i32_value(u32 tag);

	i64* get_i64_value(u32 tag);

	u32* get_u32_value(u32 tag);

	u64* get_u64_value(u32 tag);

	double* get_double_value(u32 tag);

	u8** get_string_value(u32 tag);

	u32* get_valid_value(u32 tag);

	void get(u32 tag, i32 &value);

	void get(u32 tag, u32 &value);

	void get(u32 tag, i64 &value);

	void get(u32 tag, u64 &value);

	double get_double(u32 tag);

	u8* get_string(u32 tag);

	void set_valid(u32 tag);

	int is_valid(u32 tag);

	int is_equal(u32 tag, i32 data);

	int is_equal(u32 tag, i64 data);

	int is_equal(u32 tag, u32 data);

	int is_equal(u32 tag, u64 data);

	int is_equal(u32 tag, u8* data, int size);

	void set(u32 tag, i32 data);

	void set(u32 tag, double data);

	void set(u32 tag, i64 data);

	void set(u32 tag, u64 data);

	void set(u32 tag, u32 data);

	void set(u32 tag, u8* data, int size);

	int toSize(u32 data);

	int toSize(u64 data);

	int toSize(i32 data);

	int toSize(i64 data);

	const char* forma_error_code(eFAST_ERROR code);

	const char* format_tag_operator(eFAST_OP op);

	const char* format_tag_type(eFAST_TYPE type);

	int set_error(const char* fn, fast_tag_t tag, eFAST_ERROR code,
			const char* format, ...);

	int check_codec(const char* fn);

	int check_type(const char* fn, fast_tag_t tag, u32 type);

	int bad_operator_error(const char* fn, fast_tag_t tag);

	int value_error(const char* fn, fast_tag_t tag);

	int get_presence_pap(fast_tag_t tag, u8 pset = 1);

	void reset_presence_map();

	void backup_presence_map(FASTPMap &pmap);

	void restore_presence_map(FASTPMap pmap);

	void backup_group(FASTGroup &group);

	void restore_group(FASTGroup &group);

	bool need_presence_map(fast_tag_t tag);

	int set_presence_map(fast_tag_t tag, u8 bset);

	int set_presence_map_x(fast_tag_t tag, eFAST_TYPE data_type);

	int emit_presence_map(FASTPMap *pmap, u8* data);

	void emit_byte(u8 data);

	int check_message_available(const char* fn, fast_tag_t tag, int size);

	int emit_double(fast_tag_t tag, i32 iExponent, i64 iMantissa, bool forceModify = false);

	int
			emit_string(fast_tag_t tag, u8* data, int size, bool forceModify =
					false);

	int parse_bytes(u8* data, int size);

	int parse_presence_map();

	int parse_32(fast_tag_t tag, u32* data, int sign_mask);

	int parse_64(fast_tag_t tag, u64* data, int sign_mask);

	int parse_double(fast_tag_t tag, double* data);

	void get_double_exponent_mantissa(double data, i32 &iExponent, i64 &iMantissa);
	void get_double_exponent_mantissa(const std::string & value, i32 &iExponent, i64 &iMantissa );

	int parse_string(fast_tag_t tag, u8* data, int size);

	int flush_group();

	int flush_message();

	int find_char_delta_offset(u8* a, u8* b, int size);

	FASTCodec* p_codec;
	int cl_count;
	int cl_decode;

	void verbose() {
		p_codec->verbose++;
	}

	void count(int c) {
		cl_count = c;
	}
	void decode(int d) {
		cl_decode = d;
	}
	bool isdecode() {
		return cl_decode == 1;
	}
};

class FastTag {
public:
	FastTag(eFAST_TYPE type, u32 opt, eFAST_OP op, u32 tid, u32 slot, u32 num,
			std::string value, std::string ref, std::string name) :
		m_type(type), m_req(opt), m_op(op), m_tid(tid), m_slot(slot),
				m_num(num), m_value(value), m_ref(ref), m_name(name) {
		if ((op == eFAST_OP_INCR && type == eFAST_TYPE_DECMIAL) || (op
				== eFAST_OP_INCR && type == eFAST_TYPE_ASCII) || (op
				== eFAST_OP_INCR && type == eFAST_TYPE_UNICODE) || (op
				== eFAST_OP_INCR && type == eFAST_TYPE_BYTEVECTOR) || (op
				== eFAST_OP_TAIL && type == eFAST_TYPE_I32) || (op
				== eFAST_OP_TAIL && type == eFAST_TYPE_U32) || (op
				== eFAST_OP_TAIL && type == eFAST_TYPE_I64) || (op
				== eFAST_OP_TAIL && type == eFAST_TYPE_U32) || (op
				== eFAST_OP_TAIL && type == eFAST_TYPE_DECMIAL) || (op
				== eFAST_OP_TAIL && type == eFAST_TYPE_I32)) {
			std::stringstream throwstring;
			throwstring << "Increment is not supported for for opeartor" << op
					<< ", type" << type << ", name" << name << "[" << num
					<< "]";
			throw throwstring.str();
		}
		m_tag = MAKE_TAG(m_type, m_req, m_op, m_tid, m_slot);
	}

	FastTag() {
	}

	u32 get_tid() {
		return m_tid;
	}

	u32 get_num() {
		return m_num;
	}

	std::string get_ref_tid() {
		return m_ref;
	}

	std::string get_name() {
		return m_name;
	}

	FastTag(const FastTag& copy) {
		m_type = copy.m_type;
		m_req = copy.m_req;
		m_op = copy.m_op;
		m_slot = copy.m_slot;
		m_tid = copy.m_tid;
		m_tag = copy.m_tag;
		m_num = copy.m_num;
		m_value = copy.m_value;
		m_ref = copy.m_ref;
		m_name = copy.m_name;
	}
	void reset(u32 tid, u32 slot) {
		m_tid = tid;
		m_slot = slot;
		m_tag = MAKE_TAG(m_type, m_req, m_op, m_tid, m_slot);
	}

	eFAST_TYPE m_type;
	u32 m_req;
	eFAST_OP m_op;
	u32 m_tid;
	u32 m_slot;
	u32 m_tag;
	u32 m_num;
	std::string m_value;
	std::string m_ref;
	std::string m_name;
};

/** @} */
#ifdef __cplusplus
}

#endif

#endif // _FASTApi_h_
